//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include <drvpub.h>
#include <tsc2302.h>
#include "audiocard.h"
#include "ac97codec.h"
#include "ac97link.h"

#define MIC_CHOICE_GPIO56	(1<<24)
#define AMP_PWRENB      (1<<19) // GPIO pin 115
#define SPKPA_SHUTDOWN	(1<<27) // GPIO pin

#define I2SFS_48K           0
#define I2SFS_44K           1
#define I2SFS_32K           2
#define I2SFS_24K           3
#define I2SFS_22K           4
#define I2SFS_16K           5
#define I2SFS_12K           6
#define I2SFS_11K           7
#define I2SFS_8K            8

typedef union{
    struct{
        // [0]
        unsigned short     I2SFM: 2;
        unsigned short     I2SFS: 4;
        unsigned short     MCLK: 2;
        unsigned short     MICG: 2;
        unsigned short     INMR: 2;
        unsigned short     INML: 2;
        unsigned short     HPF: 2;

        // [1]
        unsigned short     ADVR: 7;
        unsigned short     ADMUR: 1;
        unsigned short     ADVL: 7;
        unsigned short     ADMUL: 1;

        // [2]
        unsigned short     DAVR: 7;
        unsigned short     DAMUR: 1;
        unsigned short     DAVL: 7;
        unsigned short     DAMUL: 1;

        // [3]
        unsigned short     BPVR: 7;
        unsigned short     BPMUR: 1;
        unsigned short     BPVL: 7;
        unsigned short     BPMUL: 1;

        // [4]
        unsigned short     SSTEP: 1;
        unsigned short     SSRTE: 1;
        unsigned short     MONS: 1;
        unsigned short     Res4_882: 13;

        // [5]
        unsigned short     DEEMP: 1;
        unsigned short     BASS: 1;
        unsigned short     OTSYN: 1;
        unsigned short     SMPD: 1;
        unsigned short     BCKC: 1;
        unsigned short     AOSCC: 1;
        unsigned short     MIBPD: 1;
        unsigned short     PDSTS: 1;
        unsigned short     ADPDR: 1;
        unsigned short     ADPDL: 1;
        unsigned short     DAPD: 1;
        unsigned short     MOPD: 1;
        unsigned short     HAPD: 1;
        unsigned short     ABPD: 1;
        unsigned short     AVPD: 1;
        unsigned short     APD: 1;

        // [6]
        unsigned short     Res6_00: 5;
        unsigned short     IO5: 1;
        unsigned short     Res6_000: 7;
        unsigned short     GPIO5: 1;
        unsigned short     Res6_0: 2;
    }RegByName;

    unsigned short RegByNumber[7];
}PAGE2_REG;

static PAGE2_REG Page2Reg;

#ifdef DEBUG_AUDIO
void DumpTSC()
{
    int i;
    TSC_RegRead(2, 0, Page2Reg.RegByNumber, 7);
    DzDelay(1000);
    TSC_RegRead(2, 0, Page2Reg.RegByNumber, 7);
    DzDelay(1000);

    kprintf("Dump audio registers of TSC2302:\n");
    for ( i = 0; i < 7; i++) {
        kprintf("REGISTER%d = %x\n", i, Page2Reg.RegByNumber[i]);
    }
}

void DumpI2S()
{
    kprintf("Dump registers of I2S:\n");
    kprintf("SACR0 = %x\n", BV_SACR0);
    kprintf("SACR1 = %x\n", BV_SACR1);
    kprintf("SASR0 = %x\n", BV_SASR0);
    kprintf("SAIMR = %x\n", BV_SAIMR);
    kprintf("SAICR = %x\n", BV_SAICR);
    kprintf("SADIV = %x\n", BV_SADIV);
//    kprintf("SADR  = %x\n", BV_SADR);
}
#endif

void TSC_SetFrequence(unsigned long  dwFrequence)
{
    Page2Reg.RegByName.I2SFS = (unsigned short)dwFrequence;
    TSC_RegWrite(2, 0, &Page2Reg.RegByNumber[0], 1);
    DzDelay(1000);
}

void Codec_SelectAudioInChannel(AudioContext *pCxt, int channel)
{
    if (channel & (AUDIO_USE_MAINMIC | AUDIO_USE_AUXMIC)) {
        // MIC Recording
        Page2Reg.RegByName.INML  = 0;     // 0: MIC, 1: LineL, 2LineR, 3: Both Line
        Page2Reg.RegByName.INMR  = 0;
        Page2Reg.RegByName.MICG = 2;
        TSC_RegWrite(2, 0, &Page2Reg.RegByNumber[0], 1);
        DzDelay(1000);

        GPDR1 |= MIC_CHOICE_GPIO56;
	    GAFR1_U &=~(3<<16);

    	if (channel & AUDIO_USE_MAINMIC) {
            GPSR1 |= MIC_CHOICE_GPIO56;
            GPDR3 |= AMP_PWRENB;
            GAFR3_U &= ~(3<<6);
            GPCR3 |= AMP_PWRENB;
        }
        else /* if (channel & AUDIO_USE_AUXMIC)*/ {
            GPCR1 |= MIC_CHOICE_GPIO56; // on the headphone
        }
    }

}

ECode Codec_SetAudioInSource(AudioContext *pCxt, WAVEINSOURCE source)
{
    UINT flag;
    if (pCxt->waveDir == WAVEDIR_RECORDING &&
        source != pCxt->waveInSource) {
        switch (source) {
            case WAVEINSOURCE_AUXMIC:
                flag = AUDIO_USE_AUXMIC;
                break;

            case WAVEINSOURCE_MAINMIC:
            default:
                flag = AUDIO_USE_MAINMIC;
                break;
        }
        Codec_SelectAudioInChannel(pCxt, flag);
    }
    pCxt->waveInSource = source;

    return NOERROR;
}

ECode Codec_Init(AudioContext *pCxt)
{
    TSC_RegRead(2, 0, Page2Reg.RegByNumber, 7);
    DzDelay(1000);
    TSC_RegRead(2, 0, Page2Reg.RegByNumber, 7);
    DzDelay(1000);

    Page2Reg.RegByName.MCLK = 0;     // 256 times, must
    Page2Reg.RegByName.I2SFS = I2SFS_22K; // Should be changed
    Page2Reg.RegByName.I2SFM = 3;    // 20-bit, I2S, MSB first, must
    TSC_RegWrite(2, 0, &Page2Reg.RegByNumber[0], 1);
    DzDelay(1000);

    Page2Reg.RegByName.MONS = 1;     // Mono select
    TSC_RegWrite(2, 4, &Page2Reg.RegByNumber[4], 1);
    DzDelay(1000);

    Codec_Close(pCxt); // Default codec power state

    pCxt->codecStatus = 0;

#ifdef BYPASS_TEST
    // Bypass test
    Page2Reg.RegByName.APD  = 0;
	Page2Reg.RegByName.AVPD = 0;
    Page2Reg.RegByName.SMPD = 0;
	Page2Reg.RegByName.HAPD = 0;
	Page2Reg.RegByName.MIBPD = 0;
    Page2Reg.RegByName.ABPD = 0;

    TSC_RegWrite(2, 5, &Page2Reg.RegByNumber[5], 1);
	DzDelay(1000);// wait power on finished!!!

	Page2Reg.RegByName.BPVR  = 0x7f;
	Page2Reg.RegByName.BPVL = 0x7f;
	Page2Reg.RegByName.BPMUR = 0;
    Page2Reg.RegByName.BPMUL = 0;

    TSC_RegWrite(2, 3, &Page2Reg.RegByNumber[3], 1);
    DzDelay(1000);

    Codec_SelectAudioInChannel(pCxt, AUDIO_USE_MAINMIC);
    Codec_OpenAmplifier(TRUE);
    Codec_SetAudioOutVolume(pCxt, 0xff, 0xff);

#endif

	return NOERROR;
}

void Codec_Open(AudioContext *pCxt, WAVEDIR dir)
{
    DBGOUT(kprintf("Codec_Open, dir = %d, codecStatus = 0x%x\n",
        dir, pCxt->codecStatus));
    if (dir == WAVEDIR_PLAYING) {
        // If the power for playing is already on,
        // No need to open it again.
        // Otherwise there may be pops and clicks.
        if (!(CODEC_I2S_PLAYING & pCxt->codecStatus)) {
            // Playing
            Page2Reg.RegByName.APD  = 0;
        	Page2Reg.RegByName.AVPD = 0;
        	Page2Reg.RegByName.DAPD = 0;
            Page2Reg.RegByName.SMPD = 0;
        	Page2Reg.RegByName.HAPD = 0; // Headphone
        	Page2Reg.RegByName.MOPD = 0; // Speaker
            TSC_RegWrite(2, 5, &Page2Reg.RegByNumber[5], 1);
        	DzDelay(1000); // wait power on finished!!!
        	pCxt->codecStatus |= CODEC_I2S_PLAYING;
        }
    }
    else if (dir == WAVEDIR_RECORDING) {
        if (!(CODEC_I2S_RECORDING & pCxt->codecStatus)) {
            // Recording
            Page2Reg.RegByName.ADMUL = 0;
            Page2Reg.RegByName.ADMUR = 0;
            Page2Reg.RegByName.ADVL = 0x6f;
            Page2Reg.RegByName.ADVR = 0x6f;
            TSC_RegWrite(2, 1, &Page2Reg.RegByNumber[1], 1);
            DzDelay(1000);

            Page2Reg.RegByName.APD   = 0;
            Page2Reg.RegByName.AVPD  = 0;
            Page2Reg.RegByName.ADPDL = 0;
            Page2Reg.RegByName.ADPDR = 0;
            Page2Reg.RegByName.MIBPD = 0;
            Page2Reg.RegByName.SMPD  = 0;

            TSC_RegWrite(2, 5, &Page2Reg.RegByNumber[5], 1);
            DzDelay(1000); // wait power on finished!!!

            pCxt->codecStatus |= CODEC_I2S_RECORDING;
        }
    }
}

void Codec_Close(AudioContext *pCxt)
{
    DBGOUT(kprintf("Codec_Close, codecStatus = 0x%x\n", pCxt->codecStatus));

    Page2Reg.RegByName.APD  = 1;
    Page2Reg.RegByName.HAPD = 1;
	Page2Reg.RegByName.DAPD = 1;
    Page2Reg.RegByName.SMPD = 1;
	Page2Reg.RegByName.HAPD = 1;
	Page2Reg.RegByName.ADPDL = 1;
    Page2Reg.RegByName.MIBPD = 1;

	TSC_RegWrite(2, 5, &Page2Reg.RegByNumber[5], 1);
    DzDelay(1000);

	pCxt->codecStatus = 0;
}


// Write the sample rate value to the codec
// Parameters:
//      nSampleRate: the sample rate
ECode Codec_SetSampleRate(UINT rate, WAVEDIR dir)
{
    switch (rate / 1000)
    {
    case 8:
        BV_SADIV = I2S_SAM_FEQ_8;
        TSC_SetFrequence(I2SFS_8K);
        break;
    case 11:
        BV_SADIV = I2S_SAM_FEQ_11;
        TSC_SetFrequence(I2SFS_11K);
        break;
     case 16:
        BV_SADIV = I2S_SAM_FEQ_16;
        TSC_SetFrequence(I2SFS_16K);
        break;
    case 22:
        BV_SADIV = I2S_SAM_FEQ_22;
        TSC_SetFrequence(I2SFS_22K);
        break;
    case 44:
        BV_SADIV = I2S_SAM_FEQ_44;
        TSC_SetFrequence(I2SFS_44K);
        break;
     case 48:
        BV_SADIV = I2S_SAM_FEQ_48;
        TSC_SetFrequence(I2SFS_48K);
        break;
    default:
        DBGOUT(kprintf("Codec_SetSampleRate It's a bad rate!\n",
                dir, rate));
        return E_NOT_IMPLEMENTED;
    }
    DzDelay(1000);

    DBGOUT(kprintf("Codec_SetSampleRate Direction: %x  Sample Rate: %x.\n",
                dir, rate));

	return NOERROR;
}

ECode Codec_SetAudioOutVolume(AudioContext *pCxt, BYTE left, BYTE right)
{
    DBGOUT(kprintf("Codec_SetAudioOutVolume left: 0x%x, right: 0x%x\n", left, right));

    // Convert the Elastos Volume (8 bits each channel)
    //  to the codec volume (7 bits every channel)
    left >>= 1;
    right >>= 1;

    Page2Reg.RegByName.DAMUL = (left == 0 ? 1 : 0);
    Page2Reg.RegByName.DAMUR = (right == 0 ? 1 : 0);;
    Page2Reg.RegByName.DAVL = left;
    Page2Reg.RegByName.DAVR = right;

    TSC_RegWrite(2, 2, &Page2Reg.RegByNumber[2], 1);
    DzDelay(1000);

    return NOERROR;
}


void Codec_OpenAmplifier(BOOL bOpen)
{
    GPDR0 |= SPKPA_SHUTDOWN;
    if (bOpen) GPSR0 |= SPKPA_SHUTDOWN; // Open
    else GPCR0 |= SPKPA_SHUTDOWN; // Close
}

ECode Codec_InitHeadset(AudioContext *pCxt)
{
    return NOERROR;
}
